package totp;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class Base32String {
	private static final String base32String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
	private static final char[] alphabet = base32String.toCharArray();
	private static final Map<Character, Integer> map = new HashMap<>();
	static {
		for (int i = 0; i < alphabet.length; i++)
			map.put(alphabet[i], i);
	}

	public static String encode(byte[] toBeEncoded) {
		char[] result = new char[(toBeEncoded.length * 8 + 4) / 5];
		int unEncodedDigits = 8;
		int nextByteIndex = 1, i = 0;
		int currentByte = toBeEncoded[0];
		while (unEncodedDigits > 0 || nextByteIndex < toBeEncoded.length) {
			if (unEncodedDigits < 5) {
				if (nextByteIndex < toBeEncoded.length) {
					currentByte <<= 8;
					currentByte |= (toBeEncoded[nextByteIndex++] & 0xff);
					unEncodedDigits += 8;
				} else {
					int pad = 5 - unEncodedDigits;
					currentByte <<= pad;
					unEncodedDigits = 5;
				}
			}
			int index = currentByte >> (unEncodedDigits - 5) & 31;
			unEncodedDigits -= 5;
			result[i++] = alphabet[index];
		}
		return new String(result);
	}

	public static byte[] decode(String encoded) throws DecodingException {
		if (encoded.length() == 0) {
			return new byte[0];
		}
		encoded = encoded.toUpperCase(Locale.US);
		int currentByte = 0,next = 0,bits = 0;
		byte[] decoded = new byte[encoded.length() * 5 / 8];
		for (char c : encoded.toCharArray()) {
			if(!map.containsKey(c)){
				throw new DecodingException("Illegal character: " + c);
			}
			currentByte <<= 5;
			currentByte|=map.get(c)&31;
			bits+=5;
			if (bits >= 8) {
				decoded[next++] = (byte) (currentByte >> (bits - 8));
				bits -= 8;
			}
		}
		return decoded;
	}

	public static class DecodingException extends Exception {
		private static final long serialVersionUID = 1L;
		public DecodingException(String message) {
			super(message);
		}
	}

	public static String getBase32Alphabet() {
		return base32String;
	}
}
